package top.wilsonlv.jaguar.cloud.auth.extension.face;

import com.arcsoft.face.FaceFeature;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import top.wilsonlv.jaguar.cloud.auth.arcsoft.ArcFaceService;
import top.wilsonlv.jaguar.cloud.auth.sdk.dto.CredentialMatchResult;
import top.wilsonlv.jaguar.cloud.auth.sdk.enums.PrincipalType;
import top.wilsonlv.jaguar.cloud.auth.sdk.token.FaceAuthenticationToken;
import top.wilsonlv.jaguar.cloud.auth.security.UserDetailsServiceImpl;
import top.wilsonlv.jaguar.commons.data.encryption.util.EncryptionUtil;
import top.wilsonlv.jaguar.commons.web.exception.impl.CheckedException;
import top.wilsonlv.jaguar.commons.web.exception.impl.SpecialException;
import top.wilsonlv.jaguar.security.model.SecurityUser;

/**
 * @author lvws
 * @since 2022/1/6
 */
@Slf4j
@Component
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "jaguar.auth", name = "face-auth-enable", havingValue = "true")
public class FaceAuthenticationProvider implements AuthenticationProvider {

    private final UserDetailsServiceImpl userDetailsService;

    private final UserDetailsChecker userDetailsChecker;

    private final ArcFaceService arcFaceService;


    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        FaceAuthenticationToken token = (FaceAuthenticationToken) authentication;
        if (token.isAuthenticated()) {
            return token;
        }

        FaceFeature feature = arcFaceService.check((String) token.getCredentials());

        SecurityUser securityUser = null;
        if (token.getPrincipalType() == PrincipalType.DEVICE_ID) {
            SecurityUser[] candidates = userDetailsService.loadUsersByDeviceUid((String) token.getPrincipal(), null);
            for (SecurityUser candidate : candidates) {
                if (StringUtils.hasText(candidate.getFaceFeature())) {
                    FaceFeature candidateFeature = new FaceFeature(EncryptionUtil.decryptBase64(candidate.getFaceFeature()));
                    CredentialMatchResult result = arcFaceService.compareFeature(feature, candidateFeature);
                    if (result.isSuccess()) {
                        securityUser = candidate;
                        break;
                    }
                }
            }

            if (securityUser == null) {
                throw new UsernameNotFoundException(null, new SpecialException(null, "用户设备未绑定，请输入账号登录"));
            }
        } else if (token.getPrincipalType() == PrincipalType.USERNAME) {
            SecurityUser candidate = userDetailsService.loadUserByUsername((String) token.getPrincipal());
            if (StringUtils.hasText(candidate.getFaceFeature())) {
                FaceFeature candidateFeature = new FaceFeature(EncryptionUtil.decryptBase64(candidate.getFaceFeature()));
                CredentialMatchResult result = arcFaceService.compareFeature(feature, candidateFeature);
                if (result.isSuccess()) {
                    securityUser = candidate;
                }
            }
        } else {
            throw new CheckedException("无效的PrincipalType");
        }

        if (securityUser == null) {
            throw new UsernameNotFoundException("人脸认证失败");
        }
        securityUser.setCredentialsNonExpired(true);

        // 检查账号状态
        userDetailsChecker.check(securityUser);

        FaceAuthenticationToken authenticationToken = new FaceAuthenticationToken(securityUser,
                securityUser.getAuthorities());
        authenticationToken.setDetails(token.getDetails());
        return authenticationToken;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return FaceAuthenticationToken.class.isAssignableFrom(authentication);
    }
}
